﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Reflection;

using SlimDX;
using SlimDX.RawInput;
using SlimDX.Multimedia;

using RawInputDevice = SlimDX.RawInput.Device;

namespace LibSnesDotNet.Input
{
	public class RawInputDriver : InputDriver
	{
		static Dictionary<Keys, string> _keyToName;
		static Dictionary<IntPtr, int> _handleToIndex;
		static List<string> _keyboardNames;
		static List<string> _mouseNames;

		Control _control;
		bool _isEnabled;
		bool[,] _keyStates;
		bool[,] _mouseStates;

		static RawInputDriver()
		{
			_handleToIndex = new Dictionary<IntPtr, int>();
			_keyboardNames = new List<string>();
			_mouseNames = new List<string>();

			_keyToName = new Dictionary<Keys, string>();

			// Keys.KeyCode
			// Keys.Modifiers
			// Keys.None
			// Keys.LButton
			// Keys.RButton
			_keyToName.Add(Keys.Cancel, "Cancel");
			// Keys.MButton
			// Keys.XButton1
			// Keys.XButton2
			_keyToName.Add(Keys.Back, "Back");
			_keyToName.Add(Keys.Tab, "Tab");
			_keyToName.Add(Keys.LineFeed, "LineFeed");
			_keyToName.Add(Keys.Clear, "Clear");
			_keyToName.Add(Keys.ShiftKey, "Shift");
			_keyToName.Add(Keys.ControlKey, "Control");
			_keyToName.Add(Keys.Menu, "Alt");
			_keyToName.Add(Keys.Pause, "Pause");
			_keyToName.Add(Keys.JunjaMode, "JunjaMode");
			_keyToName.Add(Keys.FinalMode, "FinalMode");
			_keyToName.Add(Keys.Escape, "Escape");
			_keyToName.Add(Keys.IMEConvert, "IMEConvert");
			_keyToName.Add(Keys.IMENonconvert, "IMENonconvert");
			_keyToName.Add(Keys.IMEModeChange, "IMEModeChange");
			_keyToName.Add(Keys.Space, "Space");
			_keyToName.Add(Keys.End, "End");
			_keyToName.Add(Keys.Home, "Home");
			_keyToName.Add(Keys.Left, "Left");
			_keyToName.Add(Keys.Up, "Up");
			_keyToName.Add(Keys.Right, "Right");
			_keyToName.Add(Keys.Down, "Down");
			_keyToName.Add(Keys.Select, "Select");
			_keyToName.Add(Keys.Print, "Print");
			_keyToName.Add(Keys.Execute, "Execute");
			_keyToName.Add(Keys.Insert, "Insert");
			_keyToName.Add(Keys.Delete, "Delete");
			_keyToName.Add(Keys.Help, "Help");
			_keyToName.Add(Keys.D0, "D0");
			_keyToName.Add(Keys.D1, "D1");
			_keyToName.Add(Keys.D2, "D2");
			_keyToName.Add(Keys.D3, "D3");
			_keyToName.Add(Keys.D4, "D4");
			_keyToName.Add(Keys.D5, "D5");
			_keyToName.Add(Keys.D6, "D6");
			_keyToName.Add(Keys.D7, "D7");
			_keyToName.Add(Keys.D8, "D8");
			_keyToName.Add(Keys.D9, "D9");
			_keyToName.Add(Keys.A, "A");
			_keyToName.Add(Keys.B, "B");
			_keyToName.Add(Keys.C, "C");
			_keyToName.Add(Keys.D, "D");
			_keyToName.Add(Keys.E, "E");
			_keyToName.Add(Keys.F, "F");
			_keyToName.Add(Keys.G, "G");
			_keyToName.Add(Keys.H, "H");
			_keyToName.Add(Keys.I, "I");
			_keyToName.Add(Keys.J, "J");
			_keyToName.Add(Keys.K, "K");
			_keyToName.Add(Keys.L, "L");
			_keyToName.Add(Keys.M, "M");
			_keyToName.Add(Keys.N, "N");
			_keyToName.Add(Keys.O, "O");
			_keyToName.Add(Keys.P, "P");
			_keyToName.Add(Keys.Q, "Q");
			_keyToName.Add(Keys.R, "R");
			_keyToName.Add(Keys.S, "S");
			_keyToName.Add(Keys.T, "T");
			_keyToName.Add(Keys.U, "U");
			_keyToName.Add(Keys.V, "V");
			_keyToName.Add(Keys.W, "W");
			_keyToName.Add(Keys.X, "X");
			_keyToName.Add(Keys.Y, "Y");
			_keyToName.Add(Keys.Z, "Z");
			_keyToName.Add(Keys.LWin, "LWin");
			_keyToName.Add(Keys.RWin, "RWin");
			_keyToName.Add(Keys.Apps, "Apps");
			_keyToName.Add(Keys.Sleep, "Sleep");
			_keyToName.Add(Keys.NumPad0, "NumPad0");
			_keyToName.Add(Keys.NumPad1, "NumPad1");
			_keyToName.Add(Keys.NumPad2, "NumPad2");
			_keyToName.Add(Keys.NumPad3, "NumPad3");
			_keyToName.Add(Keys.NumPad4, "NumPad4");
			_keyToName.Add(Keys.NumPad5, "NumPad5");
			_keyToName.Add(Keys.NumPad6, "NumPad6");
			_keyToName.Add(Keys.NumPad7, "NumPad7");
			_keyToName.Add(Keys.NumPad8, "NumPad8");
			_keyToName.Add(Keys.NumPad9, "NumPad9");
			_keyToName.Add(Keys.Multiply, "Multiply");
			_keyToName.Add(Keys.Add, "Add");
			_keyToName.Add(Keys.Separator, "Separator");
			_keyToName.Add(Keys.Subtract, "Subtract");
			_keyToName.Add(Keys.Decimal, "Decimal");
			_keyToName.Add(Keys.Divide, "Divide");
			_keyToName.Add(Keys.F1, "F1");
			_keyToName.Add(Keys.F2, "F2");
			_keyToName.Add(Keys.F3, "F3");
			_keyToName.Add(Keys.F4, "F4");
			_keyToName.Add(Keys.F5, "F5");
			_keyToName.Add(Keys.F6, "F6");
			_keyToName.Add(Keys.F7, "F7");
			_keyToName.Add(Keys.F8, "F8");
			_keyToName.Add(Keys.F9, "F9");
			_keyToName.Add(Keys.F10, "F10");
			_keyToName.Add(Keys.F11, "F11");
			_keyToName.Add(Keys.F12, "F12");
			_keyToName.Add(Keys.F13, "F13");
			_keyToName.Add(Keys.F14, "F14");
			_keyToName.Add(Keys.F15, "F15");
			_keyToName.Add(Keys.F16, "F16");
			_keyToName.Add(Keys.F17, "F17");
			_keyToName.Add(Keys.F18, "F18");
			_keyToName.Add(Keys.F19, "F19");
			_keyToName.Add(Keys.F20, "F20");
			_keyToName.Add(Keys.F21, "F21");
			_keyToName.Add(Keys.F22, "F22");
			_keyToName.Add(Keys.F23, "F23");
			_keyToName.Add(Keys.F24, "F24");
			_keyToName.Add(Keys.NumLock, "NumLock");
			_keyToName.Add(Keys.Scroll, "ScrollLock");
			//_keyToName.Add(Keys.LShiftKey, "LShift");
			//_keyToName.Add(Keys.RShiftKey, "RShift");
			//_keyToName.Add(Keys.LControlKey, "LControl");
			//_keyToName.Add(Keys.RControlKey, "RControl");
			//_keyToName.Add(Keys.LMenu, "LAlt");
			//_keyToName.Add(Keys.RMenu, "RAlt");
			_keyToName.Add(Keys.BrowserBack, "BrowserBack");
			_keyToName.Add(Keys.BrowserForward, "BrowserForward");
			_keyToName.Add(Keys.BrowserRefresh, "BrowserRefresh");
			_keyToName.Add(Keys.BrowserStop, "BrowserStop");
			_keyToName.Add(Keys.BrowserSearch, "BrowserSearch");
			_keyToName.Add(Keys.BrowserFavorites, "BrowserFavorites");
			_keyToName.Add(Keys.BrowserHome, "BrowserHome");
			_keyToName.Add(Keys.VolumeMute, "VolumeMute");
			_keyToName.Add(Keys.VolumeDown, "VolumeDown");
			_keyToName.Add(Keys.VolumeUp, "VolumeUp");
			_keyToName.Add(Keys.MediaNextTrack, "MediaNextTrack");
			_keyToName.Add(Keys.MediaPreviousTrack, "MediaPreviousTrack");
			_keyToName.Add(Keys.MediaStop, "MediaStop");
			_keyToName.Add(Keys.MediaPlayPause, "MediaPlayPause");
			_keyToName.Add(Keys.LaunchMail, "LaunchMail");
			_keyToName.Add(Keys.SelectMedia, "SelectMedia");
			_keyToName.Add(Keys.LaunchApplication1, "LaunchApplication1");
			_keyToName.Add(Keys.LaunchApplication2, "LaunchApplication2");
			_keyToName.Add(Keys.Oemplus, "Plus");
			_keyToName.Add(Keys.Oemcomma, "Comma");
			_keyToName.Add(Keys.OemMinus, "Minus");
			_keyToName.Add(Keys.OemPeriod, "Period");
			_keyToName.Add(Keys.Oem8, "Oem8");
			_keyToName.Add(Keys.ProcessKey, "ProcessKey");
			_keyToName.Add(Keys.Packet, "Packet");
			_keyToName.Add(Keys.Attn, "Attn");
			_keyToName.Add(Keys.Crsel, "Crsel");
			_keyToName.Add(Keys.Exsel, "Exsel");
			_keyToName.Add(Keys.EraseEof, "EraseEof");
			_keyToName.Add(Keys.Play, "Play");
			_keyToName.Add(Keys.Zoom, "Zoom");
			// Keys.NoName
			_keyToName.Add(Keys.Pa1, "Pa1");
			_keyToName.Add(Keys.OemClear, "OemClear");
			// Keys.Shift
			// Keys.Control
			// Keys.Alt

			_keyToName.Add(Keys.Enter, "Enter"); // Keys.Return
			_keyToName.Add(Keys.CapsLock, "CapsLock"); // Keys.Capital
			_keyToName.Add(Keys.KanaMode, "KanaMode"); // Keys.HangulMode Keys.HanguelMode
			_keyToName.Add(Keys.KanjiMode, "KanjiMode"); // Keys.HanjaMode
			_keyToName.Add(Keys.IMEAccept, "IMEAccept"); // Keys.IMEAceept
			_keyToName.Add(Keys.PageUp, "PageUp"); // Keys.Prior
			_keyToName.Add(Keys.PageDown, "PageDown"); // Keys.Next
			_keyToName.Add(Keys.PrintScreen, "PrintScreen"); // Keys.Snapshot
			_keyToName.Add(Keys.OemSemicolon, "Semicolon"); // Keys.Oem1
			_keyToName.Add(Keys.OemQuestion, "Question"); // Keys.Oem2
			_keyToName.Add(Keys.Oemtilde, "Tilde"); // Keys.Oem3
			_keyToName.Add(Keys.OemOpenBrackets, "OpenBracket"); // Keys.Oem4
			_keyToName.Add(Keys.OemPipe, "Pipe"); // Keys.Oem5
			_keyToName.Add(Keys.OemCloseBrackets, "CloseBracket"); // Keys.Oem6
			_keyToName.Add(Keys.OemQuotes, "Quote"); // Keys.Oem7
			_keyToName.Add(Keys.OemBackslash, "OemBackslash"); // Keys.Oem102
		}

		public RawInputDriver(Control control)
			: base("RawInput")
		{
			if (control == null) { throw new ArgumentNullException("messageWindow"); }

			_control = control;

			RawInputDevice.RegisterDevice(UsagePage.Generic, UsageId.Keyboard, DeviceFlags.InputSink, _control.Handle);
			RawInputDevice.RegisterDevice(UsagePage.Generic, UsageId.Mouse, DeviceFlags.InputSink, _control.Handle);

			_keyStates = new bool[0, 0];
			_mouseStates = new bool[0, 0];

			_control.GotFocus += GotFocus_Handler;
			_control.LostFocus += LostFocus_Handler;
			RawInputDevice.KeyboardInput += KeyboardInput_Handler;
			RawInputDevice.MouseInput += MouseInput_Handler;

			_isEnabled = _control.Focused;
		}

		static void AddDevice(DeviceInfo di)
		{
			if (di.DeviceType == DeviceType.Keyboard)
			{
				AddKeyboard(di.Handle);
			}
			else if (di.DeviceType == DeviceType.Mouse)
			{
				AddMouse(di.Handle);
			}
		}

		static void AddKeyboard(IntPtr handle)
		{
			_handleToIndex.Add(handle, _keyboardNames.Count);
			string deviceName = "Keyboard" + _keyboardNames.Count;
			_keyboardNames.Add(deviceName);
		}

		static void AddMouse(IntPtr handle)
		{
			_handleToIndex.Add(handle, _mouseNames.Count);
			string deviceName = "Mouse" + _mouseNames.Count;
			_mouseNames.Add(deviceName);
		}

		void CheckKeyboards()
		{
			int oldCount = _keyStates.GetUpperBound(0) + 1;
			if (oldCount == _keyboardNames.Count) { return; }

			bool[,] temp = new bool[_keyboardNames.Count, 256];
			Array.Copy(_keyStates, temp, _keyStates.Length);
			_keyStates = temp;

			for (int i = oldCount; i < _keyboardNames.Count; i++)
			{
				foreach (string inputName in _keyToName.Values)
				{
					AddInput(new Input(Name, _keyboardNames[i], inputName, InputDimensionality.Digital, InputScale.Integer, InputSemantic.Absolute));
				}
			}
		}

		void CheckMice()
		{
			int oldCount = _mouseStates.GetUpperBound(0) + 1;
			if (oldCount == _mouseNames.Count) { return; }

			bool[,] temp = new bool[_mouseNames.Count, 5];
			Array.Copy(_mouseStates, temp, _mouseStates.Length);
			_mouseStates = temp;

			for (int i = oldCount; i < _mouseNames.Count; i++)
			{
				AddInput(new Input(Name, _mouseNames[i], "Delta", InputDimensionality.Planar, InputScale.Integer, InputSemantic.Relative));
				AddInput(new Input(Name, _mouseNames[i], "Wheel", InputDimensionality.Linear, InputScale.Integer, InputSemantic.Relative));
				AddInput(new Input(Name, _mouseNames[i], "Left", InputDimensionality.Digital, InputScale.Integer, InputSemantic.Absolute));
				AddInput(new Input(Name, _mouseNames[i], "Middle", InputDimensionality.Digital, InputScale.Integer, InputSemantic.Absolute));
				AddInput(new Input(Name, _mouseNames[i], "Right", InputDimensionality.Digital, InputScale.Integer, InputSemantic.Absolute));
				AddInput(new Input(Name, _mouseNames[i], "Button4", InputDimensionality.Digital, InputScale.Integer, InputSemantic.Absolute));
				AddInput(new Input(Name, _mouseNames[i], "Button5", InputDimensionality.Digital, InputScale.Integer, InputSemantic.Absolute));
			}
		}

		void GotFocus_Handler(object sender, EventArgs e)
		{
			_isEnabled = true;
		}

		void LostFocus_Handler(object sender, EventArgs e)
		{
			_isEnabled = false;

			CheckKeyboards();
			for (int i = 0; i < _keyboardNames.Count; i ++)
			{
				for (int j = 0; j < 256; j++)
				{
					if (_keyStates[i, j])
					{
						_keyStates[i, j] = false;

						OnInputReceived(new DigitalInputReceivedEventArgs(this, new Input(Name, _keyboardNames[i], _keyToName[(Keys)j],
						                                          InputDimensionality.Digital, InputScale.Integer, InputSemantic.Absolute),
						                false));
					}
				}
			}

			CheckMice();
			for (int i = 0; i < _mouseNames.Count; i++)
			{
				if (_mouseStates[i, 0])
				{
					_mouseStates[i, 0] = false;

					OnInputReceived(new DigitalInputReceivedEventArgs(this, new Input(Name, _mouseNames[i], "Left",
					                                                          InputDimensionality.Digital, InputScale.Integer, InputSemantic.Absolute),
					                                          false));
				}

				if (_mouseStates[i, 1])
				{
					_mouseStates[i, 1] = false;

					OnInputReceived(new DigitalInputReceivedEventArgs(this, new Input(Name, _mouseNames[i], "Right",
					                                                          InputDimensionality.Digital, InputScale.Integer, InputSemantic.Absolute),
					                                          false));
				}

				if (_mouseStates[i, 2])
				{
					_mouseStates[i, 2] = false;

					OnInputReceived(new DigitalInputReceivedEventArgs(this, new Input(Name, _mouseNames[i], "Middle",
					                                                          InputDimensionality.Digital, InputScale.Integer, InputSemantic.Absolute),
					                                          false));
				}

				if (_mouseStates[i, 3])
				{
					_mouseStates[i, 3] = false;

					OnInputReceived(new DigitalInputReceivedEventArgs(this, new Input(Name, _mouseNames[i], "Button4",
					                                                          InputDimensionality.Digital, InputScale.Integer, InputSemantic.Absolute),
					                                          false));
				}

				if (_mouseStates[i, 4])
				{
					_mouseStates[i, 4] = false;

					OnInputReceived(new DigitalInputReceivedEventArgs(this, new Input(Name, _mouseNames[i], "Button5",
					                                                          InputDimensionality.Digital, InputScale.Integer, InputSemantic.Absolute),
					                                          false));
				}
			}
		}

		void KeyboardInput_Handler(object sender, KeyboardInputEventArgs e)
		{
			if (!_isEnabled) { return; }
			if (e.State == KeyState.Hotkey) { return; }
			if (e.Device == IntPtr.Zero) { return; }

			int keyboard;
			if (!_handleToIndex.TryGetValue(e.Device, out keyboard))
			{
				AddKeyboard(e.Device);
				keyboard = _handleToIndex[e.Device];
			}

			CheckKeyboards();

			int key = (int)e.Key;
			bool isPressed = (e.State == KeyState.Pressed) || (e.State == KeyState.SystemKeyPressed);

			if (key >= 0 && key <= 255 && _keyStates[keyboard, key] != isPressed)
			{
				_keyStates[keyboard, key] = isPressed;

				OnInputReceived(new DigitalInputReceivedEventArgs(this, new Input(Name, _keyboardNames[keyboard], _keyToName[e.Key],
				                                          InputDimensionality.Digital, InputScale.Integer, InputSemantic.Absolute),
				                _keyStates[keyboard, key]));
			}
		}

		void MouseInput_Handler(object sender, MouseInputEventArgs e)
		{
			if (!_isEnabled) { return; }
			if (e.Device == IntPtr.Zero) { return; }

			int mouse;
			if (!_handleToIndex.TryGetValue(e.Device, out mouse))
			{
				AddMouse(e.Device);
				mouse = _handleToIndex[e.Device];
			}

			CheckMice();

			if ((e.ButtonFlags & MouseButtonFlags.MouseWheel) == MouseButtonFlags.MouseWheel)
			{
				OnInputReceived(new LinearInputReceivedEventArgs<int>(this, new Input(Name, _mouseNames[mouse], "Wheel",
				                                                              InputDimensionality.Linear, InputScale.Integer, InputSemantic.Relative),
				                                              e.WheelDelta));
			}

			if ((e.ButtonFlags & MouseButtonFlags.LeftUp) == MouseButtonFlags.LeftUp && _mouseStates[mouse, 0])
			{
				_mouseStates[mouse, 0] = false;

				OnInputReceived(new DigitalInputReceivedEventArgs(this, new Input(Name, _mouseNames[mouse], "Left",
				                                                          InputDimensionality.Digital, InputScale.Integer, InputSemantic.Absolute),
				                                          false));
			}

			if ((e.ButtonFlags & MouseButtonFlags.RightUp) == MouseButtonFlags.RightUp && _mouseStates[mouse, 1])
			{
				_mouseStates[mouse, 1] = false;

				OnInputReceived(new DigitalInputReceivedEventArgs(this, new Input(Name, _mouseNames[mouse], "Right",
				                                                          InputDimensionality.Digital, InputScale.Integer, InputSemantic.Absolute),
				                                          false));
			}

			if ((e.ButtonFlags & MouseButtonFlags.MiddleUp) == MouseButtonFlags.MiddleUp && _mouseStates[mouse, 2])
			{
				_mouseStates[mouse, 2] = false;

				OnInputReceived(new DigitalInputReceivedEventArgs(this, new Input(Name, _mouseNames[mouse], "Middle",
				                                                          InputDimensionality.Digital, InputScale.Integer, InputSemantic.Absolute),
				                                          false));
			}

			if ((e.ButtonFlags & MouseButtonFlags.Button4Up) == MouseButtonFlags.Button4Up && _mouseStates[mouse, 3])
			{
				_mouseStates[mouse, 3] = false;

				OnInputReceived(new DigitalInputReceivedEventArgs(this, new Input(Name, _mouseNames[mouse], "Button4",
				                                                          InputDimensionality.Digital, InputScale.Integer, InputSemantic.Absolute),
				                                          false));
			}

			if ((e.ButtonFlags & MouseButtonFlags.Button5Up) == MouseButtonFlags.Button5Up && _mouseStates[mouse, 4])
			{
				_mouseStates[mouse, 4] = false;

				OnInputReceived(new DigitalInputReceivedEventArgs(this, new Input(Name, _mouseNames[mouse], "Button5",
				                                                          InputDimensionality.Digital, InputScale.Integer, InputSemantic.Absolute),
				                                          false));
			}

			if (_control.RectangleToScreen(_control.ClientRectangle).Contains(Cursor.Position))
			{
				if (e.Mode == MouseMode.RelativeMovement && (e.X != 0 || e.Y != 0))
				{
					OnInputReceived(new PlanarInputReceivedEventArgs<int>(this, new Input(Name, _mouseNames[mouse], "Delta",
					                                                              InputDimensionality.Planar, InputScale.Integer, InputSemantic.Relative),
					                                              e.X, e.Y));
				}

				if ((e.ButtonFlags & MouseButtonFlags.LeftDown) == MouseButtonFlags.LeftDown && !_mouseStates[mouse, 0])
				{
					_mouseStates[mouse, 0] = true;

					OnInputReceived(new DigitalInputReceivedEventArgs(this, new Input(Name, _mouseNames[mouse], "Left",
					                                                          InputDimensionality.Digital, InputScale.Integer, InputSemantic.Absolute),
					                                          true));
				}

				if ((e.ButtonFlags & MouseButtonFlags.RightDown) == MouseButtonFlags.RightDown && !_mouseStates[mouse, 1])
				{
					_mouseStates[mouse, 1] = true;

					OnInputReceived(new DigitalInputReceivedEventArgs(this, new Input(Name, _mouseNames[mouse], "Right",
					                                                          InputDimensionality.Digital, InputScale.Integer, InputSemantic.Absolute),
					                                          true));
				}

				if ((e.ButtonFlags & MouseButtonFlags.MiddleDown) == MouseButtonFlags.MiddleDown && !_mouseStates[mouse, 2])
				{
					_mouseStates[mouse, 2] = true;

					OnInputReceived(new DigitalInputReceivedEventArgs(this, new Input(Name, _mouseNames[mouse], "Middle",
					                                                          InputDimensionality.Digital, InputScale.Integer, InputSemantic.Absolute),
					                                          true));
				}

				if ((e.ButtonFlags & MouseButtonFlags.Button4Down) == MouseButtonFlags.Button4Down && !_mouseStates[mouse, 3])
				{
					_mouseStates[mouse, 3] = true;

					OnInputReceived(new DigitalInputReceivedEventArgs(this, new Input(Name, _mouseNames[mouse], "Button4",
					                                                          InputDimensionality.Digital, InputScale.Integer, InputSemantic.Absolute),
					                                          true));
				}

				if ((e.ButtonFlags & MouseButtonFlags.Button5Down) == MouseButtonFlags.Button5Down && !_mouseStates[mouse, 4])
				{
					_mouseStates[mouse, 4] = true;

					OnInputReceived(new DigitalInputReceivedEventArgs(this, new Input(Name, _mouseNames[mouse], "Button5",
					                                                          InputDimensionality.Digital, InputScale.Integer, InputSemantic.Absolute),
					                                          true));
				}
			}
		}

		public override void Dispose()
		{
			RawInputDevice.KeyboardInput -= KeyboardInput_Handler;
			RawInputDevice.MouseInput -= MouseInput_Handler;
			_control.GotFocus -= GotFocus_Handler;
			_control.LostFocus -= LostFocus_Handler;
		}

	}
}
